package com.zhg.shortlink.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhg.shortlink.domain.entity.ShortUrlMapping;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.Duration;

import static com.zhg.shortlink.config.RedisKeyConstants.URL_HASH_MAPPING_KEY;

/**
 * 多级缓存服务
 * 本地缓存 + Redis缓存
 * @author 朱洪刚
 * @version 1.0
 * @data 2025/10/20 17:01
 */
@RequiredArgsConstructor
@Service
public class CacheService {

    private static final Logger logger = LoggerFactory.getLogger(CacheService.class);

    private static final String CACHE_PREFIX = "shortlink:";
    private static final String URL_CACHE_KEY = CACHE_PREFIX + "url:";
    private static final String COUNT_CACHE_KEY = CACHE_PREFIX + "count:";
    private static final String BLOOM_FILTER_KEY = CACHE_PREFIX + "bloom";

    private static final Duration DEFAULT_EXPIRE_TIME = Duration.ofHours(1);
    private static final Duration HOT_DATA_EXPIRE_TIME = Duration.ofHours(24);

    private final RedisTemplate<String, String> redisTemplate;
    private final ObjectMapper objectMapper;

    private final TieredBloomFilterService tieredBloomFilterService;

    private final LocalCacheService localCacheService;


    /**
     * 统一缓存接口 - 从多级缓存获取短链信息
     * 先查本地缓存，再查Redis缓存
     */
    public ShortUrlMapping getFromCache(String shortCode) {
        // 参数校验
        if (!StringUtils.hasText(shortCode)) {
            logger.warn("shortCode为空，无法获取缓存");
            return null;
        }
        // 先从本地缓存获取
        ShortUrlMapping shortUrlMapping = localCacheService.getFromLocalCache(shortCode);
        if (shortUrlMapping != null) {
            logger.debug("本地缓存命中: {}", shortCode);
            return shortUrlMapping;
        }

        // 本地缓存未命中，从Redis获取
        shortUrlMapping = getFromRedis(shortCode);
        if (shortUrlMapping != null) {
            logger.debug("Redis缓存命中: {}", shortCode);
            // 将Redis数据放入本地缓存
            localCacheService.putToLocalCache(shortCode, shortUrlMapping);
        }

        return shortUrlMapping;
    }

    /**
     * 统一缓存接口 - 将短链信息放入多级缓存
     */
    public void putToCache(String shortCode, ShortUrlMapping shortUrlMapping) {
        // 放入本地缓存
        localCacheService.putToLocalCache(shortCode, shortUrlMapping);
        // 放入Redis缓存
        cacheToRedis(shortCode, shortUrlMapping);
        logger.debug("缓存短链信息: {}", shortCode);
    }


    /**
     * 从Redis获取短链信息
     */
    public ShortUrlMapping getFromRedis(String shortCode) {
        try {
            String key = URL_CACHE_KEY + shortCode;
            String json = redisTemplate.opsForValue().get(key);
            if (json != null) {
                return objectMapper.readValue(json, ShortUrlMapping.class);
            }
        } catch (JsonProcessingException e) {
            logger.error("Redis反序列化失败: {}", e.getMessage());
        } catch (Exception e) {
            logger.error("Redis查询失败: {}", e.getMessage());
        }
        return null;
    }
    /**
     * 缓存短链信息到Redis
     */
    public void cacheToRedis(String shortCode, ShortUrlMapping shortUrlMapping) {
        try {
            String key = URL_CACHE_KEY + shortCode;
            String json = objectMapper.writeValueAsString(shortUrlMapping);

            // 热点数据缓存时间更长
            Duration expireTime = isHotData(shortUrlMapping) ? HOT_DATA_EXPIRE_TIME : DEFAULT_EXPIRE_TIME;
            redisTemplate.opsForValue().set(key, json, expireTime);

            logger.debug("Redis缓存成功: {}", shortCode);
        } catch (JsonProcessingException e) {
            logger.error("Redis序列化失败: {}", e.getMessage());
        } catch (Exception e) {
            logger.error("Redis缓存失败: {}", e.getMessage());
        }
    }

    /**
     * 缓存访问次数
     */
    public void cacheAccessCount(String shortCode, Long count) {
        try {
            String key = COUNT_CACHE_KEY + shortCode;
            redisTemplate.opsForValue().set(key, count.toString(), DEFAULT_EXPIRE_TIME);
        } catch (Exception e) {
            logger.error("缓存访问次数失败: {}", e.getMessage());
        }
    }

    /**
     * 获取缓存的访问次数
     */
    public Long getCachedAccessCount(String shortCode) {
        try {
            String key = COUNT_CACHE_KEY + shortCode;
            String count = redisTemplate.opsForValue().get(key);
            return count != null ? Long.parseLong(count) : null;
        } catch (Exception e) {
            logger.error("获取缓存访问次数失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 增加访问次数缓存
     */
    public Long incrementAccessCount(String shortCode) {
        try {
            String key = COUNT_CACHE_KEY + shortCode;
            return redisTemplate.opsForValue().increment(key);
        } catch (Exception e) {
            logger.error("增加访问次数失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 删除缓存
     */
    @CacheEvict(value = "shortUrls", key = "#shortCode")
    public void evictCache(String shortCode) {
        try {
            // 删除Redis缓存
            String urlKey = URL_CACHE_KEY + shortCode;
            String countKey = COUNT_CACHE_KEY + shortCode;
            redisTemplate.delete(urlKey);
            redisTemplate.delete(countKey);
            logger.debug("删除缓存: {}", shortCode);
        } catch (Exception e) {
            logger.error("删除缓存失败: {}", e.getMessage());
        }
    }

    /**
     * 预热缓存
     */
    public void warmUpCache(String shortCode, ShortUrlMapping shortUrlMapping) {
        cacheToRedis(shortCode, shortUrlMapping);
        cacheAccessCount(shortCode, shortUrlMapping.getAccessCount());
    }

    /**
     * 检查是否为热点数据
     */
    private boolean isHotData(ShortUrlMapping shortUrlMapping) {
        return shortUrlMapping.getAccessCount() > 1000; // 访问次数超过1000认为是热点数据
    }





    /**
     * 批量预热缓存
     */
    public void batchWarmUp(java.util.List<ShortUrlMapping> shortUrlMappings) {
        shortUrlMappings.parallelStream().forEach(shortUrlMapping -> {
            try {
                warmUpCache(shortUrlMapping.getShortCode(), shortUrlMapping);
            } catch (Exception e) {
                logger.error("批量预热缓存失败: {}", e.getMessage());
            }
        });
    }

    /**
     * 清理过期缓存
     */
    public void cleanExpiredCache() {
        // 这里可以实现定时清理过期缓存的逻辑
        logger.info("开始清理过期缓存");
        // 实际实现可以扫描Redis中的过期键并删除
    }

    /**
     * 获取缓存统计信息
     */
    public String getCacheStats() {
        // 返回缓存命中率等统计信息
        return "缓存统计信息 - 待实现";
    }

    /**
     * 添加到布隆过滤器,使用Set模拟
     */
    public void addToBloomFilter(String shortCode) {
        tieredBloomFilterService.put(shortCode); // 使用异步添加
    }

    /**
     * 布隆过滤器检查接口
     */
    public boolean existsInBloomFilter(String shortCode) {
        return tieredBloomFilterService.mightContain(shortCode);
    }

    /**
     * 缓存原始URL哈希值到短链code的映射
     */
    public void putUrlHashMapping(String originUrlHash, String shortCode) {
        try {
            String key = URL_HASH_MAPPING_KEY + originUrlHash;
            redisTemplate.opsForValue().set(key, shortCode, DEFAULT_EXPIRE_TIME);
            logger.debug("缓存URL哈希映射: originUrlHash={}, shortCode={}", originUrlHash, shortCode);
        } catch (Exception e) {
            logger.error("缓存URL哈希映射失败: originUrlHash={}, shortCode={}", originUrlHash, shortCode, e);
        }
    }

    /**
     * 根据原始URL哈希值获取短链code
     */
    public String getShortCodeByUrlHash(String originUrlHash) {
        try {
            String key = URL_HASH_MAPPING_KEY + originUrlHash;
            String shortCode = redisTemplate.opsForValue().get(key);
            if (shortCode != null) {
                logger.debug("URL哈希映射缓存命中: originUrlHash={}, shortCode={}", originUrlHash, shortCode);
            }
            return shortCode;
        } catch (Exception e) {
            logger.error("获取URL哈希映射失败: originUrlHash={}", originUrlHash, e);
            return null;
        }
    }

    /**
     * 删除URL哈希映射缓存
     */
    public void removeUrlHashMapping(String originUrlHash) {
        try {
            String key = URL_HASH_MAPPING_KEY + originUrlHash;
            redisTemplate.delete(key);
            logger.debug("删除URL哈希映射缓存: originUrlHash={}", originUrlHash);
        } catch (Exception e) {
            logger.error("删除URL哈希映射缓存失败: originUrlHash={}", originUrlHash, e);
        }
    }


}
